/**
 * @author mrdoob / http://mrdoob.com/
 */

import * as THREE from '../../js/threejs/build/three.module.js'

import {TransformControls} from '../../js/threejs/jsm/controls/TransformControls.js'

import {UIPanel} from '../../js/threejs/libs/ui.js'

import {EditorControls} from '../../js/threejs/EditorControls.js'
import {ViewportInfo} from './Viewport.Info.js'
import {ViewHelper} from './Viewport.ViewHelper.js'

import {SetPositionCommand} from '../../js/threejs/commands/SetPositionCommand.js'
import {SetRotationCommand} from '../../js/threejs/commands/SetRotationCommand.js'
import {SetScaleCommand} from '../../js/threejs/commands/SetScaleCommand.js'

var Viewport = function (editor) {
  var signals = editor.signals

  var container = new UIPanel()
  container.setId('viewport')
  container.setPosition('absolute')

  container.add(new ViewportInfo(editor))

  var renderer = null
  var pmremGenerator = null

  var camera = editor.camera
  var scene = editor.scene
  var sceneHelpers = editor.sceneHelpers
  var showSceneHelpers = true
  var enableTransformControls = true

  var objects = []

  // helpers

  var viewHelper = new ViewHelper(camera, container)

  var box = new THREE.Box3()

  var selectionBox = new THREE.BoxHelper()
  selectionBox.material.depthTest = false
  selectionBox.material.transparent = true
  selectionBox.visible = false
  sceneHelpers.add(selectionBox)

  var objectPositionOnDown = null
  var objectRotationOnDown = null
  var objectScaleOnDown = null

  var transformControls = new TransformControls(camera, container.dom)
  transformControls.addEventListener('change', function () {
    var object = transformControls.object

    if (object !== undefined) {
      selectionBox.setFromObject(object)

      var helper = editor.helpers[object.id]

      if (helper !== undefined && helper.isSkeletonHelper !== true) {
        helper.update()
      }

      signals.refreshSidebarObject3D.dispatch(object)
    }

    render()
  })
  transformControls.addEventListener('mouseDown', function () {
    var object = transformControls.object

    objectPositionOnDown = object.position.clone()
    objectRotationOnDown = object.rotation.clone()
    objectScaleOnDown = object.scale.clone()

    controls.enabled = false
  })
  transformControls.addEventListener('mouseUp', function () {
    var object = transformControls.object

    if (object !== undefined) {
      switch (transformControls.getMode()) {
        case 'translate':
          if (!objectPositionOnDown.equals(object.position)) {
            editor.execute(new SetPositionCommand(editor, object, object.position, objectPositionOnDown))
          }

          break

        case 'rotate':
          if (!objectRotationOnDown.equals(object.rotation)) {
            editor.execute(new SetRotationCommand(editor, object, object.rotation, objectRotationOnDown))
          }

          break

        case 'scale':
          if (!objectScaleOnDown.equals(object.scale)) {
            editor.execute(new SetScaleCommand(editor, object, object.scale, objectScaleOnDown))
          }

          break
      }
    }

    controls.enabled = true
  })

  sceneHelpers.add(transformControls)

  // object picking

  var raycaster = new THREE.Raycaster()
  var mouse = new THREE.Vector2()

  // events

  function getIntersects(point, objects) {
    mouse.set(point.x * 2 - 1, -(point.y * 2) + 1)

    raycaster.setFromCamera(mouse, camera)

    return raycaster.intersectObjects(objects)
  }

  var onDownPosition = new THREE.Vector2()
  var onUpPosition = new THREE.Vector2()
  var onDoubleClickPosition = new THREE.Vector2()

  function getMousePosition(dom, x, y) {
    var rect = dom.getBoundingClientRect()
    return [(x - rect.left) / rect.width, (y - rect.top) / rect.height]
  }

  function handleClick() {
    if (onDownPosition.distanceTo(onUpPosition) === 0) {
      let intersects = getIntersects(onUpPosition, objects)
      if (intersects.length > 0) {
        let object = editor.getWholeObject(intersects[0].object)
        if (!enableTransformControls && (object.userData.type === 'scene')) {
          // zhouwr, 数据展示页，点击对象是整个现场场景时，不选中
          editor.select(null)
        } else {
          if (object.userData.object !== undefined) {
            editor.select(object.userData.object)
          } else {
            editor.select(object)
          }
        }
      } else {
        editor.select(null)
      }

      render()
    }
  }

  function onMouseDown(event) {
    // event.preventDefault();

    let array = getMousePosition(container.dom, event.clientX, event.clientY)
    onDownPosition.fromArray(array)

    document.addEventListener('mouseup', onMouseUp, false)
  }

  function onMouseUp(event) {
    let array = getMousePosition(container.dom, event.clientX, event.clientY)
    onUpPosition.fromArray(array)

    handleClick()

    document.removeEventListener('mouseup', onMouseUp, false)
  }

  function onTouchStart(event) {
    let touch = event.changedTouches[0]

    let array = getMousePosition(container.dom, touch.clientX, touch.clientY)
    onDownPosition.fromArray(array)

    document.addEventListener('touchend', onTouchEnd, false)
  }

  function onTouchEnd(event) {
    let touch = event.changedTouches[0]

    let array = getMousePosition(container.dom, touch.clientX, touch.clientY)
    onUpPosition.fromArray(array)

    handleClick()

    document.removeEventListener('touchend', onTouchEnd, false)
  }

  function onDoubleClick(event) {
    let array = getMousePosition(container.dom, event.clientX, event.clientY)
    onDoubleClickPosition.fromArray(array)

    let intersects = getIntersects(onDoubleClickPosition, objects)

    if (intersects.length > 0) {
      let object = editor.getWholeObject(intersects[0].object)

      signals.objectFocused.dispatch(object)
    }
  }

  container.dom.addEventListener('mousedown', onMouseDown, false)
  container.dom.addEventListener('touchstart', onTouchStart, false)
  container.dom.addEventListener('dblclick', onDoubleClick, false)

  // controls need to be added *after* main logic,
  // otherwise controls.enabled doesn't work.

  var controls = new EditorControls(camera, container.dom)
  controls.addEventListener('change', function () {
    signals.cameraChanged.dispatch(camera)
  })

  viewHelper.controls = controls

  // signals

  signals.transformModeChanged.add(function (mode) {
    transformControls.setMode(mode)
  })

  signals.snapChanged.add(function (dist) {
    transformControls.setTranslationSnap(dist)
  })

  signals.spaceChanged.add(function (space) {
    transformControls.setSpace(space)
  })

  signals.rendererUpdated.add(function () {
    render()
  })

  signals.rendererChanged.add(function (newRenderer, newPmremGenerator) {
    if (renderer !== null) {
      container.dom.removeChild(renderer.domElement)
    }

    renderer = newRenderer
    pmremGenerator = newPmremGenerator

    renderer.autoClear = false
    renderer.autoUpdateScene = false
    renderer.outputEncoding = THREE.sRGBEncoding
    renderer.setPixelRatio(window.devicePixelRatio)
    renderer.setSize(container.dom.offsetWidth, container.dom.offsetHeight)

    container.dom.appendChild(renderer.domElement)

    render()
  })

  signals.sceneGraphChanged.add(function () {
    render()
  })

  signals.cameraChanged.add(function () {
    render()
  })

  signals.objectSelected.add(function (object) {
    selectionBox.visible = false
    transformControls.detach()

    if (object !== null && object !== scene && object !== camera) {
      box.setFromObject(object)

      if (box.isEmpty() === false) {
        selectionBox.setFromObject(object)
        selectionBox.visible = true
      }

      if (enableTransformControls) {
        transformControls.attach(object)
      }
    }

    render()
  })

  signals.objectFocused.add(function (object) {
    controls.focus(object)
  })

  signals.geometryChanged.add(function (object) {
    if (object !== undefined) {
      selectionBox.setFromObject(object)
    }

    render()
  })

  /**
   * zhouwr add by 2020-11-05
   * 增加此代码，用于点击对象时选中对象
   */
  signals.objectAdded.add(function (object) {
    object.traverse(function (child) {
      objects.push(child)
      if (child.type !== 'AmbientLight')  {
        child.receiveShadow = true
        child.castShadow = true
        // child.material = new THREE.MeshLambertMaterial({color: 0x00ffff})
      }
    })
  })

  signals.objectChanged.add(function (object) {
    if (editor.selected === object) {
      selectionBox.setFromObject(object)
    }

    if (object.isPerspectiveCamera) {
      object.updateProjectionMatrix()
    }

    if (editor.helpers[object.id] !== undefined) {
      editor.helpers[object.id].update()
    }

    render()
  })

  signals.objectRemoved.add(function (object) {
    controls.enabled = true // see #14180
    if (object === transformControls.object) {
      transformControls.detach()
    }

    objects.splice(objects, 1)

    // object.traverse( function ( child ) {
    //
    // 	objects.splice( objects.indexOf( child ), 1 );
    //
    // } );
  })

  signals.helperAdded.add(function (object) {
    var picker = object.getObjectByName('picker')

    if (picker !== undefined) {
      objects.push(picker)
    }
  })

  signals.helperRemoved.add(function (object) {
    var picker = object.getObjectByName('picker')

    if (picker !== undefined) {
      objects.splice(objects.indexOf(picker), 1)
    }
  })

  signals.materialChanged.add(function () {
    render()
  })

  // background

  var currentBackgroundType = null

  signals.sceneBackgroundChanged.add(function (backgroundType, backgroundColor, backgroundTexture, backgroundCubeTexture, backgroundEquirectTexture) {
    if (currentBackgroundType !== backgroundType) {
      switch (backgroundType) {
        case 'None':
          scene.background = null
          break
        case 'Color':
          scene.background = new THREE.Color()
          break
      }
    }

    if (backgroundType === 'Color') {
      scene.background.set(backgroundColor)
      scene.environment = null
    } else if (backgroundType === 'Texture') {
      scene.background = backgroundTexture
      scene.environment = null
    } else if (backgroundType === 'CubeTexture') {
      if (backgroundCubeTexture && backgroundCubeTexture.isHDRTexture) {
        var texture = pmremGenerator.fromCubemap(backgroundCubeTexture).texture
        texture.isPmremTexture = true

        scene.background = texture
        scene.environment = texture
      } else {
        scene.background = backgroundCubeTexture
        scene.environment = null
      }
    } else if (backgroundType === 'Equirect') {
      if (backgroundEquirectTexture && backgroundEquirectTexture.isHDRTexture) {
        var texture = pmremGenerator.fromEquirectangular(backgroundEquirectTexture).texture
        texture.isPmremTexture = true

        scene.background = texture
        scene.environment = texture
      } else {
        scene.background = null
        scene.environment = null
      }
    }

    render()
  })

  // fog

  var currentFogType = null

  signals.sceneFogChanged.add(function (fogType, fogColor, fogNear, fogFar, fogDensity) {
    if (currentFogType !== fogType) {
      switch (fogType) {
        case 'None':
          scene.fog = null
          break
        case 'Fog':
          scene.fog = new THREE.Fog()
          break
        case 'FogExp2':
          scene.fog = new THREE.FogExp2()
          break
      }

      currentFogType = fogType
    }

    if (scene.fog) {
      if (scene.fog.isFog) {
        scene.fog.color.setHex(fogColor)
        scene.fog.near = fogNear
        scene.fog.far = fogFar
      } else if (scene.fog.isFogExp2) {
        scene.fog.color.setHex(fogColor)
        scene.fog.density = fogDensity
      }
    }

    render()
  })

  signals.windowResize.add(function () {
    // TODO: Move this out?
    editor.DEFAULT_CAMERA.aspect = container.dom.offsetWidth / container.dom.offsetHeight
    editor.DEFAULT_CAMERA.updateProjectionMatrix()

    camera.aspect = container.dom.offsetWidth / container.dom.offsetHeight
    camera.updateProjectionMatrix()

    renderer.setSize(container.dom.offsetWidth, container.dom.offsetHeight)

    render()
  })

  signals.showHelpersChanged.add(function (showHelpers) {
    showSceneHelpers = showHelpers
    transformControls.enabled = showHelpers

    render()
  })

  // animations

  var clock = new THREE.Clock() // only used for animations

  function animate() {
    requestAnimationFrame(animate)

    var mixer = editor.mixer
    var delta = clock.getDelta()

    var needsUpdate = false

    if (mixer.stats.actions.inUse > 0) {
      mixer.update(delta)
      needsUpdate = true
    }

    if (viewHelper.animating === true) {
      viewHelper.update(delta)
      needsUpdate = true
    }

    if (needsUpdate === true) render()
  }

  requestAnimationFrame(animate)

  //

  var startTime = 0
  var endTime = 0

  function render() {
    startTime = performance.now()

    renderer.setViewport(0, 0, container.dom.offsetWidth, container.dom.offsetHeight)
    renderer.render(scene, editor.camera)

    if (camera === editor.camera) {
      renderer.autoClear = false
      if (showSceneHelpers === true) renderer.render(sceneHelpers, camera)
      viewHelper.render(renderer)
      renderer.autoClear = true
    }

    endTime = performance.now()
    var frametime = endTime - startTime
    editor.signals.sceneRendered.dispatch(frametime)
  }

  return container
}

export {Viewport}
